 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.menus;

 import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.core.commands.common.HandleObject;
 import org.eclipse.core.commands.common.HandleObjectManager;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.ui.internal.misc.Policy;

 /**
  * <p>
  * Manages a group of menu elements. These menu elements include things like
  * menus, groups, and items. The manager is responsible for handling the layout
  * of these items with respect to each other. Visibility is controlled
  * externally by updating the visibility of the menu elements themselves. The
  * painting of these menu elements on the application window(s) is handler by a
  * renderer.
  * </p>
  * <p>
  * Clients may instantiate, but must not extend.
  * </p>
  * <p>
  * <strong>PROVISIONAL</strong>. This class or interface has been added as
  * part of a work in progress. There is a guarantee neither that this API will
  * work nor that it will remain the same. Please do not use this API without
  * consulting with the Platform/UI team.
  * </p>
  * <p>
  * This class will eventually exist in <code>org.eclipse.jface.menus</code>.
  * </p>
  *
  * @since 3.2
  */
 public final class SMenuManager extends HandleObjectManager implements
         IPropertyChangeListener {

     /**
      * The map of action set identifiers (<code>String</code>) to action set (
      * <code>SActionSet</code>). This collection may be empty, but it is
      * never <code>null</code>.
      */
     private final Map actionSetsById = new HashMap ();

     /**
      * The set of action sets that are defined. This value may be empty, but it
      * is never <code>null</code>.
      */
     private final Set definedActionSets = new HashSet ();

     /**
      * The set of groups that are defined. This value may be empty, but it is
      * never <code>null</code>.
      */
     private final Set definedGroups = new HashSet ();

     /**
      * The set of identifiers for those menus that are defined. This value may
      * be empty, but it is never <code>null</code>.
      */
     private final Set definedMenus = new HashSet ();

     /**
      * The set of identifiers for those widgets that are defined. This value may
      * be empty, but it is never <code>null</code>.
      */
     private final Set definedWidgets = new HashSet ();

     /**
      * The map of group identifiers (<code>String</code>) to groups (
      * <code>SGroup</code>). This collection may be empty, but it is never
      * <code>null</code>.
      */
     private final Map groupsById = new HashMap ();

     /**
      * The layout for this menu manager. This layout is <code>null</code> if
      * the menu manager has changed since the last layout was built.
      */
     private SMenuLayout layout = null;

     /**
      * The map of menu identifiers (<code>String</code>) to menus (
      * <code>SMenu</code>). This collection may be empty, but it is never
      * <code>null</code>.
      */
     private final Map menusById = new HashMap ();

     /**
      * The map of widget identifiers (<code>String</code>) to widgets (
      * <code>SWidget</code>). This collection may be empty, but it is never
      * <code>null</code>.
      */
     private final Map widgetsById = new HashMap ();

     /**
      * Adds a listener to this menu manager that will be notified when this
      * manager's state changes.
      *
      * @param listener
      * The listener to be added; must not be <code>null</code>.
      */
     public final void addListener(final IMenuManagerListener listener) {
         addListenerObject(listener);
     }

     /**
      * Notifies listeners to this menu manager that it has changed in some way.
      *
      * @param event
      * The event to fire; may be <code>null</code>.
      */
     private final void fireMenuManagerChanged(final MenuManagerEvent event) {
         if (event == null) {
             return;
         }

         final Object [] listeners = getListeners();
         for (int i = 0; i < listeners.length; i++) {
             final IMenuManagerListener listener = (IMenuManagerListener) listeners[i];
             listener.menuManagerChanged(event);
         }
     }

     /**
      * Gets the action set with the given identifier. If no such action set
      * currently exists, then the action set will be created (but will be
      * undefined).
      *
      * @param actionSetId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The action set with the given identifier; this value will never
      * be <code>null</code>, but it might be undefined.
      * @see SActionSet
      */
     public final SActionSet getActionSet(final String actionSetId) {
         checkId(actionSetId);

         SActionSet actionSet = (SActionSet) actionSetsById.get(actionSetId);
         if (actionSet == null) {
             actionSet = new SActionSet(actionSetId);
             actionSetsById.put(actionSetId, actionSet);
             actionSet.addListener(this);
         }

         return actionSet;
     }

     /**
      * Returns those action sets that are defined.
      *
      * @return The defined action sets; this value may be empty, but it is never
      * <code>null</code>.
      */
     public final SActionSet[] getDefinedActionSets() {
         return (SActionSet[]) definedActionSets
                 .toArray(new SActionSet[definedActionSets.size()]);
     }

     /**
      * Returns those groups that are defined.
      *
      * @return The defined groups; this value may be empty, but it is never
      * <code>null</code>.
      */
     public final SGroup[] getDefinedGroups() {
         return (SGroup[]) definedGroups
                 .toArray(new SGroup[definedGroups.size()]);
     }

     /**
      * Returns those items that are defined.
      *
      * @return The defined items; this value may be empty, but it is never
      * <code>null</code>.
      */
     public final SItem[] getDefinedItems() {
         return (SItem[]) definedHandleObjects
                 .toArray(new SItem[definedHandleObjects.size()]);
     }

     /**
      * Returns those menus that are defined.
      *
      * @return The defined menus; this value may be empty, but it is never
      * <code>null</code>.
      */
     public final SMenu[] getDefinedMenus() {
         return (SMenu[]) definedMenus.toArray(new SMenu[definedMenus.size()]);
     }

     /**
      * Returns those widgets that are defined.
      *
      * @return The defined widgets; this value may be empty, but it is never
      * <code>null</code>.
      */
     public final SWidget[] getDefinedWidgets() {
         return (SWidget[]) definedWidgets.toArray(new SWidget[definedWidgets
                 .size()]);
     }

     /**
      * Gets the group with the given identifier. If no such group currently
      * exists, then the group will be created (but will be undefined).
      *
      * @param groupId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The group with the given identifier; this value will never be
      * <code>null</code>, but it might be undefined.
      * @see SGroup
      */
     public final SGroup getGroup(final String groupId) {
         checkId(groupId);

         if (Policy.EXPERIMENTAL_MENU
                 && groupId.indexOf(LeafLocationElement.BREAKPOINT_PATH)>-1) {
             System.err.println("getMenu: " + groupId); //$NON-NLS-1$
 }
         SGroup group = (SGroup) groupsById.get(groupId);
         if (group == null) {
             group = new SGroup(groupId);
             groupsById.put(groupId, group);
             group.addListener(this);
         }

         return group;
     }

     /**
      * Gets the item with the given identifier. If no such item currently
      * exists, then the item will be created (but will be undefined).
      *
      * @param itemId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The item with the given identifier; this value will never be
      * <code>null</code>, but it might be undefined.
      * @see SItem
      */
     public final SItem getItem(final String itemId) {
         checkId(itemId);

         SItem item = (SItem) handleObjectsById.get(itemId);
         if (item == null) {
             item = new SItem(itemId);
             handleObjectsById.put(itemId, item);
             item.addListener(this);
         }

         return item;
     }

     /**
      * <p>
      * Retrieves the layout for the menu elements held by this menu manager.
      * This layout does not consider visibility or whether the elements are
      * currently shown. It is simply the layout if everything was visible and
      * showing. It also does not consider dynamic menu elements, which will be
      * asked to make changes to the layout before the menu element is shown.
      * </p>
      * <p>
      * The result of this computation is cached between subsequent calls. So, if
      * no changes are made to the menu elements, the layout can be retrieved in
      * constant time. Otherwise, it will take <code>O(n)</code> time to
      * compute, where <code>n</code> is the number of menu elements held by
      * this manager.
      * </p>
      *
      * @return The menu layout; never <code>null</code>.
      */
     public final SMenuLayout getLayout() {
 // if (layout == null) {
 // Generate the layout
 final Collection menuElements = new ArrayList (definedMenus.size()
                     + definedGroups.size() + definedHandleObjects.size()
                     + definedWidgets.size());
             menuElements.addAll(definedMenus);
             menuElements.addAll(definedGroups);
             menuElements.addAll(definedHandleObjects);
             menuElements.addAll(definedWidgets);
             layout = SMenuLayout.computeLayout(menuElements);
 // }

         return layout;
     }

     /**
      * Gets the menu with the given identifier. If no such menu currently
      * exists, then the menu will be created (but will be undefined).
      *
      * @param menuId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The menu with the given identifier; this value will never be
      * <code>null</code>, but it might be undefined.
      * @see SMenu
      */
     public final SMenu getMenu(final String menuId) {
         checkId(menuId);

         if (Policy.EXPERIMENTAL_MENU
                 && menuId.indexOf(LeafLocationElement.BREAKPOINT_PATH)>-1) {
             System.err.println("getMenu: " + menuId); //$NON-NLS-1$
 }
         
         SMenu menu = (SMenu) menusById.get(menuId);
         if (menu == null) {
             menu = new SMenu(menuId);
             menusById.put(menuId, menu);
             menu.addListener(this);
         }

         return menu;
     }

     /**
      * Gets the widget with the given identifier. If no such widget currently
      * exists, then the widget will be created (but will be undefined).
      *
      * @param widgetId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The item with the given identifier; this value will never be
      * <code>null</code>, but it might be undefined.
      * @see SWidget
      */
     public final SWidget getWidget(final String widgetId) {
         checkId(widgetId);

         SWidget widget = (SWidget) widgetsById.get(widgetId);
         if (widget == null) {
             widget = new SWidget(widgetId);
             widgetsById.put(widgetId, widget);
             widget.addListener(this);
         }

         return widget;
     }

     public final void propertyChange(final PropertyChangeEvent event) {
         if (MenuElement.PROPERTY_DEFINED.equals(event.getProperty())) {
             final HandleObject handle = (HandleObject) event.getSource();
             final String id = handle.getId();
             final boolean added = ((Boolean ) event.getNewValue())
                     .booleanValue();
             if (added) {
                 if (handle instanceof SActionSet) {
                     definedActionSets.add(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, null, false, null, false, null, false,
                                 id, added));
                     }
                 } else if (handle instanceof SGroup) {
                     definedGroups.add(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, id,
                                 added, null, false, null, false, null, false,
                                 null, false));
                     }
                 } else if (handle instanceof SItem) {
                     definedHandleObjects.add(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, id, added, null, false, null, false,
                                 null, false));
                     }
                 } else if (handle instanceof SMenu) {
                     definedMenus.add(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, null, false, id, added, null, false,
                                 null, false));
                     }
                 } else if (handle instanceof SWidget) {
                     definedWidgets.add(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, null, false, null, false, id, added,
                                 null, false));
                     }
                 }
             } else {
                 if (handle instanceof SActionSet) {
                     definedActionSets.remove(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, null, false, null, false, null, false,
                                 id, added));
                     }
                 } else if (handle instanceof SGroup) {
                     definedGroups.remove(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, id,
                                 added, null, false, null, false, null, false,
                                 null, false));
                     }
                 } else if (handle instanceof SItem) {
                     definedHandleObjects.remove(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, id, added, null, false, null, false,
                                 null, false));
                     }
                 } else if (handle instanceof SMenu) {
                     definedMenus.remove(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, null, false, id, added, null, false,
                                 null, false));
                     }
                 } else if (handle instanceof SWidget) {
                     definedWidgets.remove(handle);
                     if (isListenerAttached()) {
                         fireMenuManagerChanged(new MenuManagerEvent(this, null,
                                 false, null, false, null, false, id, added,
                                 null, false));
                     }
                 }
             }
         }
     }

     /**
      * Removes a listener from this menu manager.
      *
      * @param listener
      * The listener to be removed; must not be <code>null</code>.
      */
     public final void removeListener(final IMenuManagerListener listener) {
         removeListenerObject(listener);
     }
 }

